﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using Emgu.CV;
using Emgu.CV.Structure;
using System.IO;
using System.Xml;
using System.Threading;
using System.Threading.Tasks;
using Emgu.CV.CvEnum;

namespace FaceDetection
{
    public partial class Form1 : Form
    {

        int applicationIdleIndex = 0;
        bool dolog = false;
       
        public Form1()
        {
            InitializeComponent();
            ENC_Parameters.Param[ 0 ] = ENC;
            Image_Encoder_JPG = GetEncoder( ImageFormat.Jpeg );
            initialise_capture( );
            stopCapturing.Enabled = false;
            face_PICBX.Image = face_PICBX.InitialImage;

            //TriggetCapture( );
        }

        void FrameOnlyCamera ( object sender , EventArgs e ) {
            currentFrame = cap.QueryFrame( ).Resize( 320 , 240 , Emgu.CV.CvEnum.INTER.CV_INTER_CUBIC );
            if ( currentFrame != null ) {
                    image_PICBX.Image = currentFrame.ToBitmap( );
            }
        }
        
        void FrameGrabber ( object sender , EventArgs e ) {
            log(string.Format( "Framegrabber started {0}" , ++applicationIdleIndex ));
            //Application.Idle -= new EventHandler( FrameGrabber );
            //Get the current frame form capture device
            currentFrame = cap.QueryFrame( ).Resize( 320 , 240 , Emgu.CV.CvEnum.INTER.CV_INTER_CUBIC );
            
            
            log(string.Format( "Framegrabber started1 {0}" , applicationIdleIndex ));
            //Convert it to Grayscale
            if ( currentFrame != null ) {
            
                log(string.Format( "Framegrabber started2 {0}" , applicationIdleIndex ));
                gray_frame = currentFrame.Convert<Gray , Byte>( );

                //Face Detector
                //MCvAvgComp[ ][ ] facesDetected = gray_frame.DetectHaarCascade( Face , 1.2 , 10 , Emgu.CV.CvEnum.HAAR_DETECTION_TYPE.DO_CANNY_PRUNING , new Size( 20 , 20 ) );
                MCvAvgComp[ ] facesDetected = Face.Detect( gray_frame , 1.2 , 10 , Emgu.CV.CvEnum.HAAR_DETECTION_TYPE.DO_CANNY_PRUNING , new Size( 20 , 20 ) );
                log(string.Format( "Framegrabber started3 {0}, facefound:{1}" , applicationIdleIndex, facesDetected.Length ));

                //Action for each element detected
                foreach ( MCvAvgComp face_found in facesDetected) {

                    result = currentFrame.Copy( face_found.rect ).Convert<Gray , byte>( ).Resize( 100 , 100 , Emgu.CV.CvEnum.INTER.CV_INTER_CUBIC );
                    result._EqualizeHist( );
                    face_PICBX.Image = result.ToBitmap( );
                    //draw the face detected in the 0th (gray) channel with blue color
                    currentFrame.Draw( face_found.rect , new Bgr( Color.Red ) , 2 );

                }
                log(string.Format( "Framegrabber started4 {0}" , applicationIdleIndex ));
                if ( (!RECORD) && facesDetected.Length > 0 && resultImages.Count < num_faces_to_aquire ) {
                    resultImages.Add( result );
                    count_lbl.Text = "Count: " + resultImages.Count.ToString( );
                    if ( resultImages.Count == num_faces_to_aquire ) {
                        RegisterUser.Enabled = true;
                        //NEXT_BTN.Visible = true;
                        //PREV_btn.Visible = true;
                        //count_lbl.Visible = false;
                        //Single_btn.Visible = true;
                        //ADD_ALL.Visible = true;
                        RECORD = true;
                        //Application.Idle -= new EventHandler( FrameGrabber );
                        Thread.Sleep( 100 );
                    }
                }

                image_PICBX.Image = currentFrame.ToBitmap( );
                log(string.Format( "Framegrabber ended {0}" , applicationIdleIndex ));
            }
        }

        private ImageCodecInfo GetEncoder ( ImageFormat format ) {
            ImageCodecInfo[ ] codecs = ImageCodecInfo.GetImageDecoders( );
            foreach ( ImageCodecInfo codec in codecs ) {
                if ( codec.FormatID == format.Guid ) {
                    return codec;
                }
            }
            return null;
        }

        private void RegisterUser_Click ( object sender , EventArgs e ) {
            if ( NAME_PERSON.Text.Trim( ).Equals( string.Empty ) ) {
                MessageBox.Show( "Enter User name/Id to register." );
            } else {
                ( sender as Button ).Enabled = false;
                save_training_data( face_PICBX.Image );
                MessageBox.Show( "User Registed !" );

                AfterRegisterUser( );
            }
        }

        private void AfterRegisterUser ( ) {
            face_PICBX.Image = face_PICBX.InitialImage;
            stop_capture( );
            Application.Idle += new EventHandler( FrameOnlyCamera );
            StartCapture.Enabled = true;
            stopCapturing.Enabled = false;
            ValidateUser.Enabled = true;
            NAME_PERSON.Text = "";
        }

        private void StartCapture_Click ( object sender , EventArgs e ) {
            Application.Idle -= new EventHandler( FrameOnlyCamera );
            TriggetCapture( );
            StartCapture.Enabled = false;
            stopCapturing.Enabled = true;
            ValidateUser.Enabled = false;
        }

        private void TriggetCapture ( ) {
            Application.Idle += new EventHandler( FrameGrabber );
        }

        private bool save_training_data ( Image face_data ) {
            try {
                Random rand = new Random( );
                bool file_create = true;
                string facename = "face_" + NAME_PERSON.Text + "_" + rand.Next( ).ToString( ) + ".jpg";
                while ( file_create ) {

                    if ( !File.Exists( Application.StartupPath + "/TrainedFaces/facename" ) ) {
                        file_create = false;
                    } else {
                        facename = "face_" + NAME_PERSON.Text + "_" + rand.Next( ).ToString( ) + ".jpg";
                    }
                }


                if ( Directory.Exists( Application.StartupPath + "/TrainedFaces/" ) ) {
                    face_data.Save( Application.StartupPath + "/TrainedFaces/" + facename , ImageFormat.Jpeg );
                } else {
                    Directory.CreateDirectory( Application.StartupPath + "/TrainedFaces/" );
                    face_data.Save( Application.StartupPath + "/TrainedFaces/" + facename , ImageFormat.Jpeg );
                }
                if ( File.Exists( Application.StartupPath + "/TrainedFaces/TrainedLabels.xml" ) ) {
                    //File.AppendAllText(Application.StartupPath + "/TrainedFaces/TrainedLabels.txt", NAME_PERSON.Text + "\n\r");
                    bool loading = true;
                    while ( loading ) {
                        try {
                            docu.Load( Application.StartupPath + "/TrainedFaces/TrainedLabels.xml" );
                            loading = false;
                        } catch {
                            docu = null;
                            docu = new XmlDocument( );
                            Thread.Sleep( 10 );
                        }
                    }

                    //Get the root element
                    XmlElement root = docu.DocumentElement;

                    XmlElement face_D = docu.CreateElement( "FACE" );
                    XmlElement name_D = docu.CreateElement( "NAME" );
                    XmlElement file_D = docu.CreateElement( "FILE" );

                    //Add the values for each nodes
                    //name.Value = textBoxName.Text;
                    //age.InnerText = textBoxAge.Text;
                    //gender.InnerText = textBoxGender.Text;
                    name_D.InnerText = NAME_PERSON.Text;
                    file_D.InnerText = facename;

                    //Construct the Person element
                    //person.Attributes.Append(name);
                    face_D.AppendChild( name_D );
                    face_D.AppendChild( file_D );

                    //Add the New person element to the end of the root element
                    root.AppendChild( face_D );

                    //Save the document
                    docu.Save( Application.StartupPath + "/TrainedFaces/TrainedLabels.xml" );
                    //XmlElement child_element = docu.CreateElement("FACE");
                    //docu.AppendChild(child_element);
                    //docu.Save("TrainedLabels.xml");
                } else {
                    FileStream FS_Face = File.OpenWrite( Application.StartupPath + "/TrainedFaces/TrainedLabels.xml" );

                    using ( XmlWriter writer = XmlWriter.Create( FS_Face ) ) {
                        writer.WriteStartDocument( );
                        writer.WriteStartElement( "Faces_For_Training" );

                        writer.WriteStartElement( "FACE" );
                        writer.WriteElementString( "NAME" , NAME_PERSON.Text );
                        writer.WriteElementString( "FILE" , facename );
                        writer.WriteEndElement( );

                        writer.WriteEndElement( );
                        writer.WriteEndDocument( );
                    }
                    FS_Face.Close( );
                }

                return true;
            } catch ( Exception ex ) {
                return false;
            }

        }

        private void ValidateUser_Click ( object sender , EventArgs e ) {

            Application.Idle -= new EventHandler( FrameOnlyCamera);

            if ( Eigen_Recog == null )
                Eigen_Recog = new Classifier_Train( );

            if ( Eigen_Recog.IsTrained ) {
                message_bar.Text = "Training Data loaded";
            } else {
                message_bar.Text = "No training data found, please train program using Train menu option";
            }

            Application.Idle += new EventHandler( FrameGrabber_Parrellel );
        }   

        void FrameGrabber_Parrellel ( object sender , EventArgs e ) {

            //Get the current frame form capture device 
            currentFrame = cap.QueryFrame( ).Resize( 320 , 240 , Emgu.CV.CvEnum.INTER.CV_INTER_CUBIC );

            //Convert it to Grayscale
            //Clear_Faces_Found();

            if ( currentFrame != null ) {
                gray_frame = currentFrame.Convert<Gray , Byte>( );

                //Face Detector
                MCvAvgComp[ ] facesDetected = Face.Detect( gray_frame , 1.2 , 10 , Emgu.CV.CvEnum.HAAR_DETECTION_TYPE.DO_CANNY_PRUNING , new Size( 50 , 50 ) );
                count_lbl.Text = "faceDetected : " + facesDetected.Count().ToString();
                
                Parallel.ForEach( facesDetected, (faceFound,loopState) => {
                    //foreach ( MCvAvgComp faceFound in facesDetected ) {
                    try {
                        result = currentFrame.Copy( faceFound.rect ).Convert<Gray , byte>( ).Resize( 100 , 100 , Emgu.CV.CvEnum.INTER.CV_INTER_CUBIC );
                        result._EqualizeHist( );

                        currentFrame.Draw( faceFound.rect , new Bgr( Color.Blue ) , 2 );

                        if ( Eigen_Recog.IsTrained ) {
                            string name = Eigen_Recog.Recognise( result );
                            if ( name != null && name != string.Empty ) { // if face not found, continue facedetection again
                                ADD_Face_Found( result , name );
                                loopState.Stop( );
                                Application.Idle -= new EventHandler( FrameGrabber_Parrellel );
                            }
                        }
                    } catch(Exception ex) {
                        count_lbl.Text = ex.Message;
                    }
                } );

                //Show the faces procesed and recognized
                image_PICBX.Image = currentFrame.ToBitmap( );
            }
        }

        private void ADD_Face_Found ( Image<Gray , byte> result , string name ) {
            message_bar.Text = "Welcome : {" + cnt++.ToString() + "} " + name;
        }

        private void stopCapturing_Click ( object sender , EventArgs e ) {
            stop_capture( );
            Application.Idle += new EventHandler( FrameOnlyCamera );

            StartCapture.Enabled = true;
            stopCapturing.Enabled = false;
            RegisterUser.Enabled = false;
            face_PICBX.Image = face_PICBX.InitialImage;
            ValidateUser.Enabled = true;
        }

        private void reset_Click ( object sender , EventArgs e ) {
            Reset( );
        }

        private void Reset ( ) {
            message_bar.Text = "";
            count_lbl.Text = "";
            NAME_PERSON.Text = "";
            RegisterUser.Enabled = false;
            ValidateUser.Enabled = true;
            stopCapturing.Enabled = false;
            stop_capture( );
            initialise_capture( );
            //TriggetCapture( );
        }

        private void Form1_FormClosing ( object sender , FormClosingEventArgs e ) {
            stop_capture( );

            if ( cap != null )
                cap.Dispose( );
        }

        private void stop_capture ( ) {
            Application.Idle -= new EventHandler( FrameGrabber );
        }

        public void initialise_capture ( ) {
            if ( cap == null ) {
                cap = new Capture( );

                cap.QueryFrame( );
            }
            Application.Idle += new EventHandler( FrameOnlyCamera );
            //Initialize the FrameGraber event

        }

        public void log ( string str ) {
            
            if ( dolog )
                message_bar.Text += "\n" + str;
        }
    }

}
